package com.qf.shop.item.controller;

import com.alibaba.fastjson.JSON;
import com.netflix.discovery.converters.Auto;
import com.qf.shop.common.exception.BusinessException;
import com.qf.shop.common.vo.GoodsVo;
import com.qf.shop.item.feign.api.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.ScriptExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/item")
@Slf4j
public class ItemController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private final String ITEM_PRE = "shop:item:";

    private final String ITEM_LOCK = "shop:lock:item";

    @Autowired
    private GoodsService goodsService;

    // item/10.html

    /**
     * 先查询缓存，缓存查不到再查数据库
     * 1、使用分布式锁、锁自动释放
     * 2、拿到锁后处理义务出现了异常
     * 3、释放锁要使用luna脚本，需要保证原子性
     * 4、获取到锁后执行业务超时的情况(启动一个定时任务来给这个锁续期)
     * 5、缓存穿透
     * a)出现的场景:用户在查询一条数据，缓存中没有，数据库中也没有，必须商品id是-1，100
     * b)如何解决
     * 1)如果数据库也没有，需要给缓存设置默认值
     * 6、缓存击穿
     * a)出现的场景：缓存中没有，但是数据库中有
     * b)如何解决，分布式锁来解决
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}.html")
    public String page(@PathVariable Integer id, ModelMap modelMap) {
        log.debug("id:{}", id);

        String goods = getGoods(id);

        if ("null".equals(goods)) {
            log.debug("商品没有查询到，抛出异常了，【{}】", Thread.currentThread().getId());
            throw new BusinessException("该商品不存在", 102);
        }

        // 把JSON字符串转成对象
        GoodsVo goodsVo = JSON.parseObject(goods, GoodsVo.class);

        // 放到域中
        modelMap.addAttribute("goodsVo", goodsVo);

        // 返回视图页面
        return "item";
    }

//    private String getGoods(Integer id) {
//        // 1.先查询缓存
//        String goods = redisTemplate.opsForValue().get(ITEM_PRE + id);
//
//        // 2.判断缓存是否为空
//        if (StringUtils.isEmpty(goods)) {
//            String uuid = UUID.randomUUID().toString();
//            try {
//                // 先获取锁(排队)，考虑到自动释放
//                Boolean lock = redisTemplate.opsForValue().setIfAbsent(ITEM_LOCK, uuid, 1, TimeUnit.SECONDS);
//                if (lock) { // 判断是否拿到锁了
//                    // 3.查询数据库
//                    GoodsVo goodsVo = goodsService.getGoodsVoById(id);
//
//                    // 解决缓存穿透的问题
//                    if (goods != null) {
//                        // 把对象转成JSON字符串，然后在存在缓存中
//                        goods = JSON.toJSONString(goodsVo);
//                        // 4、添加到缓存中
//                        redisTemplate.opsForValue().set(ITEM_PRE + id, goods, 7, TimeUnit.DAYS);
//                        // 返回从数据库中查询的数据
//                    } else {
//                        goods = "null";
//                        redisTemplate.opsForValue().set(ITEM_PRE + id, goods, 7, TimeUnit.DAYS);
//                    }
//                    return goods;
//                } else {
//                    try {
//                        Thread.sleep(300);
//                        getGoods(id);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                }
//            } finally {
//
////                String redisUiid = redisTemplate.opsForValue().get(ITEM_LOCK);
////                if (uuid.equals(redisUiid)) {
////                // 手动释放锁
////                    redisTemplate.delete(ITEM_LOCK);
////                }
//
//                String lunaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
//                        "then\n" +
//                        "    return redis.call(\"del\",KEYS[1])\n" +
//                        "else\n" +
//                        "    return 0\n" +
//                        "end";
//
//                // 使用luna脚本释放锁，因为luna脚本是原子性的
//                DefaultRedisScript defaultRedisScript = new DefaultRedisScript(lunaScript, Long.class);
//                redisTemplate.execute(defaultRedisScript, Arrays.asList(ITEM_LOCK), uuid);
//            }
//        }
//        return goods;
//    }

    @Autowired
    private RedissonClient redissonClient;

    // 使用redisson实现分布式锁
    private String getGoods(Integer id) {

        // 1.先查询缓存
        String goods = redisTemplate.opsForValue().get(ITEM_PRE + id);

        // 获取锁
        RLock itemLock = redissonClient.getLock(ITEM_LOCK); // hash结构 key[item-lock] feid[uuid] value[1]

        // 2.判断缓存是否为空
        if (StringUtils.isEmpty(goods)) {
            // 加锁，加锁失败等待1s就返回
            boolean lock = false;
            try {
                lock = itemLock.tryLock(1, 1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
//                e.printStackTrace();
                log.error("获取锁失败", e);
            }
            if (lock) {
                try {
                    // 3.查询数据库
                    GoodsVo goodsVo = goodsService.getGoodsVoById(id);

                    // 解决缓存穿透的问题
                    if (goodsVo != null) {
                        // 把对象转成JSON字符串，然后在存在缓存中
                        goods = JSON.toJSONString(goodsVo);
                        // 4、添加到缓存中
                        redisTemplate.opsForValue().set(ITEM_PRE + id, goods, 7, TimeUnit.DAYS);
                        // 返回从数据库中查询的数据
                    } else {
                        goods = "null";
                        redisTemplate.opsForValue().set(ITEM_PRE + id, goods, 7, TimeUnit.DAYS);
                    }
                } finally {
                    // 释放锁
                    itemLock.unlock();
                }
            } else {
                try {
                    Thread.sleep(300);
                    getGoods(id);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return goods;
    }
}